<?php
/***
 * Candy框架 节点类
 * 
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2019-12-05 16:03:32 $   
 */

declare (strict_types = 1);
namespace Candy\Core;

defined('CANDY') OR die('You Are A Bad Guy. o_O???');

final Class Node {
	private static $methods = [];		//操作列表
	private static $objCacheList = [];	//映射列表
	
	/**
     * 获取标准访问节点
	 *
     * @param string $node
     * @return string
     */
    public static function full(string $node = null): string
    {
        if(empty($node)) return self::current();
        if(stripos($node, '/') === false){
            $node = C('APPNAME') . '/' . C('CONTROL') . '/' . $node;
        }else{
			$nodes = explode('/', $node);
			if(count($nodes) == 2){
				$node = C('APPNAME') . '/' . $nodes[0] . '/' . $nodes[1];
			}
        }
        return self::parseString(trim($node, " /"));
    }
	
	/**
     * 获取当前访问节点
	 *
     * @return string
     */
    public static function current(): string
    {
        return self::parseString(C('APPNAME') . '/' . C('CONTROL') . '/' . C('ACTION'));
    }
	
	/**
     * 强制验证访问权限
     * 
     * @param null|string $node
     * @return boolean
     */
    public static function forceAuth(string $node = null,array $auths = []): bool
    {
		//取出默认操作
        $real = is_null($node) ? self::current() : self::full($node);
		
		//解析出应用操作
        list($appName, $control, $action) = explode('/', $real);
		
		//验证权限
		$auth = self::getClassList($appName)[$control]['actions'][$action];
		
		self::getActionInfo();
		
		//默认超级管理员角色
        if(session('userRole') === 'Admin') return true;
		
		if($auth['auth']){
			count($auths) == 0 && $auths = session('userAuths') ?: [];
			if(!(isset($auths[$appName]) && isset($auths[$appName][$control]) && in_array($action, $auths[$appName][$control]))){
				return false;
			}
		}
        return true;
    }
	
	/**
     * 检查指定节点授权
     *
     * @param null|string $node
     * @return boolean
     */
    public static function checkAuth(string $node = null,array $auths = []): bool
    {
		//默认超级管理员角色
        if(session('userRole') === 'Admin') return true;
		
		//取出默认操作
        $real = is_null($node) ? self::current() : self::full($node);
		
		list(,$control) = explode('/', $real);
		
        if (isset(self::getAuthList()[$control]['auths'][$real])){
			empty($auths) && $auths = session('userAuths') ?? [];
            return in_array($real, $auths);
        } else {
            return true;
        }
    }
	
	/**
     * 获取当前节点信息
     */
    public static function getActionInfo(): void
    {
		$auth = self::getClassList(C('APPNAME'));
		C('actinfo',[
			'title'=>$auth[C('CONTROL')]['actions'][C('ACTION')]['title'],
			'describe'=>$auth[C('CONTROL')]['actions'][C('ACTION')]['describe'],
			'log'=>$auth[C('CONTROL')]['actions'][C('ACTION')]['log'],
		]);
	}

	/**
     * 获取授权节点列表
	 *
     * @return array
     */
    public static function getAuthList(string $appName = null): array
    {
        static $nodes = [];
		
		if(isset($nodes[$appName])) return $nodes[$appName];
		
		//默认appName
		empty($appName) && $appName = C('APPNAME');
		
		//有数据直接返回
        if(isset($nodes[$appName])) return $nodes[$appName];
		
		//非Debug模式下 取缓存值
		if(isDebug() === false){
			$nodes = G('nodeAuthList');
			if(isset($nodes[$appName])) return $nodes[$appName];
		}
		foreach(self::getClassList($appName) as $node => $info){			
			foreach($info['actions'] as $method => $auth){
				if($auth['auth']){
					$key = $appName .'/'.$node.'/'.$method;
					$nodes[$appName][$auth['group']]['name'] = $info['name'];
					$nodes[$appName][$auth['group']]['auths'][$key] = $auth['title'];
				}
			}
		}
		
		//非Debug模式下 存缓存值
		if(isDebug() === false)
			G('nodeAuthList', $nodes, 0);
		
        return $nodes[$appName] ?: [];
    }

	/**
     * 获取菜单节点列表
	 *
     * @return array
     */
    public static function getMenuList(string $appName = null,bool $force = false): array
    {
        static $nodes = [];
		
		//默认appName
		empty($appName) && $appName = C('APPNAME');
		
		//有数据直接返回
        if(isset($nodes[$appName])) return $nodes[$appName];
		
		//非Debug模式下 取缓存值
		if(isDebug() === false){
			$nodes = G('nodeMenuList');
			if(isset($nodes[$appName])) return $nodes[$appName];
		}
		
		//对象列表
		$classList = self::getClassList($appName);
		
		//权限
		$auths = session('userRole') === 'Admin' ? true : ($force ? session('userAuths') : []);
		
		foreach($classList as $node => $info){
			foreach($info['actions'] as $method => $menu){
				if($menu['menu']){
					if(is_array($auths) && !(isset($auths[$appName]) && isset($auths[$appName][$node]) && in_array($method, $auths[$appName][$node])))	continue;
					
					$key = $appName .'/'.$node.'/'.$method;
					
					if($node == $menu['group'])
						$nodes[$appName][$menu['group']]['name'] = $info['name'];
					
					$nodes[$appName][$menu['group']]['menus'][$key] = $menu['title'];
				}
			}
		}
		//非Debug模式下 存缓存值
		if(isDebug() === false){
			G('nodeMenuList', $nodes, 0);
		}
		
        return $nodes[$appName] ?? [];
    }
	
	/**
     * 获取方法节点列表
	 *
     * @return array
     */
    public static function getClassList(string $appName = null, $type = null): array
    {
        static $nodes = [];
		
		//指定默认appName
		empty($appName) && $appName = C('APPNAME');
        
		//有值直接返回
		if(isset($nodes[$appName])) return $nodes[$appName] ?: [];
		
		//非Debug模式下 取缓存值
		if(isDebug() === false){
			$nodes = G('nodeMethodList');
			if(isset($nodes[$appName])) return $nodes[$appName];
		}
        
		//获取当前应用所有反射对象
        $objCacheList = self::eachController($appName, $type);
		
		if(count($objCacheList) > 0){
			//循环处理
			foreach($objCacheList as $className=>$reflection){
				//操作名
				$action = str_replace('Action', '', $reflection->getShortName());
				
				//检查分类
				$classGroup = '';
				if($reflection->hasConstant('classGroup'))
					$classGroup = $reflection->hasConstant('classGroup');
				
				//标题
				$title = '';
				if(empty($classGroup) || $classGroup == $action){
					//获取名称
					$titleSrt = $reflection->getDocComment();
					
					if($titleSrt){
						preg_match('|@name(.*)\*|isU', $titleSrt, $matches);
					}else{
						$file = $reflection->getFileName();
						preg_match('|@name(.*)\*|isU', fileGetContents($file), $matches);
					}
					
					//名称
					$title = $matches[1] ?? $action.'控制器';
				}
				
				$nodes[$appName][$action] = [
					'name'=>$title,
					'actions'=>self::getMethodList($appName, $className)
				];
			}
		}else{
			$nodes[$appName] = false;
		}
		
		//非Debug模式下 存缓存值
		if(isDebug() === false)
			G('nodeMethodList', $nodes, 0);
		
        return $nodes[$appName] ?: [];
    }
	
	/**
     * 获取所有有效APP节点
	 *
     * @return array
     */
    public static function getAllNodeList(): array
    {
		static $nodes = [];
		
		if(!empty($nodes)) return $nodes ?: [];
		
		//获取APP目录
		$apps = dirs(CANDYROOT . 'Application');
		
		//循环获取
		foreach($apps as $appName){
			if(isset($nodes[$appName])) continue;
			$methodList = self::getClassList($appName);
			if($methodList != false){
				$nodes[$appName] = [
					'name'=>$appName.'项目',
					'controls'=>$methodList
				];
			}
		}
		
		//启用的Addon
		$addons = Addon::getEnableAddons(1);
		
		if(count($addons) > 0)
			foreach($addons as $appName=>$addon){
				if(isset($nodes[$appName])) continue;
				$methodList = self::getClassList($appName, 'addon');
				if($methodList != false){
					$nodes[$appName] = [
						'name'=>$addon['name'],
						'controls'=>$methodList
					];
				}
			}
		
		return $nodes ?: [];
	}
	
	/**
     * 获取所有验证权限的节点
	 *
     * @return array
     */
    public static function getAllAuthList(): array
    {
		static $auths = [];
		
		if(!empty($auths)) return $auths;
		
		//APP权限
		$appAuths = self::getAppAuthList();
		
		//Addon权限
		$addonAuths = self::getAddonAuthList();
		
		//合并菜单
		$auths = array_merge($auths, $appAuths, $addonAuths);
		
		return $auths;
	}
	
	/**
     * 获取所有APP验证权限的节点
	 *
     * @return array
     */
    public static function getAppAuthList(): array
    {
		static $auths = [];
		
		if(!empty($auths)) return $auths;
		
		//获取APP目录
		$apps = dirs(CANDYROOT . 'Application');
		
		//APP名称
		$appNames = loadConfig('App');
		
		//循环获取
		foreach($apps as $appName){
			if(isset($auths[$appName])) continue;
			$authlist = self::getAuthList($appName);
			if($authlist)
				$auths[$appName] = [
						'name'=>isset($appNames[$appName]) ? $appNames[$appName]['name'] : $appName,
						'list'=>$authlist,
					];
		}
		
		return $auths;
	}
	/**
     * 获取所有Addon验证权限的节点
	 *
     * @return array
     */
    public static function getAddonAuthList(): array
    {
		static $auths = [];
		
		if(!empty($auths)) return $auths;
		
		//启用的Addon
		$addons = Addon::getEnableAddons(1);
		if(count($addons) > 0)
			foreach($addons as $appName=>$addon){
				if(isset($auths[$appName])) continue;
				$authlist = self::getAuthList($appName);
				if($authlist)
					$auths[$appName] = $authlist;
			}
		
		return $auths;
	}
	
	/**
     * 保存权限节点列表
	 *
     * @return array
     */
    private static function takeAuthList(): array
    {
		static $authlist = [];
		
		if(!empty($authlist)) return $authlist;
		
		//非Debug模式下 取缓存值
		if(isDebug() === false){
			$authlist = G('checkAuthList');
			if(!empty($authlist)) return $authlist;
		}
		
		$auths = self::getAllAuthList();
		
		foreach($auths as $app){
			foreach($app as $control){
				foreach($control['auths'] as $auth){
					$authlist[] = $auth;
				}
			}
		}
		
		//存放节点缓存
		if(isDebug() === false)
			G('checkAuthList', $authlist);
		
		return $authlist;
	}
	
	/**
     * 获取所有菜单节点
	 *
     * @return array
     */
    public static function getAllMenuList(): array
    {
		static $menus = [];
		
		if(!empty($menus)) return $menus;
		
		//APP菜单
		$appMenus = self::getAppMenuList();
		
		//Addon菜单
		$addonMenus = self::getAddonMenuList();
		
		//合并菜单
		$menus = array_merge($menus, $appMenus, $addonMenus);
		
		return $menus;
	}
	
	/**
     * 获取所有APP菜单节点
	 *
     * @return array
     */
    public static function getAppMenuList(): array
    {
		static $menus = [];
		
		//不允许列表
		if(is_null(G('appNode')) || empty(G('appNode')['menu'])){
			return [];
		}else{
			$appNode = G('appNode')['menu'];
		}
		
		if(!empty($menus)) return $menus;
		
		//获取APP目录
		$apps = dirs(CANDYROOT . 'Application');
		
		//APP排序
		$appOrder = loadConfig('App');
		
		if(count($appOrder) > 0){
			foreach($appOrder as $appName=>$info){
				if(in_array($appName, $apps)){
					if(isset($menus[$appName]) || !in_array($appName, $appNode)) continue;
					
					//移除
					$key = array_keys($apps, $appName)[0];
					unset($apps[$key]);
					
					//获取菜单
					$menulist = self::getMenuList($appName, true);
					if($menulist)
						$menus[$appName] = [
							'name'=>$info['name'],
							'controls'=>$menulist,
						];
				}
				
				
			}
		}
		
		
		//循环获取
		foreach($apps as $appName){
			if(isset($menus[$appName]) || !in_array($appName, $appNode)){
				continue;
			} 
			$menulist = self::getMenuList($appName);
			
			if($menulist)
				$menus[$appName] = [
					'name'=>$appName . '菜单列表',
					'controls'=>$menulist,
				];
		}
		
		return $menus;
	}
	
	/**
     * 获取所有Addon菜单节点
	 *
     * @return array
     */
    public static function getAddonMenuList(): array
    {
		static $menus = [];
		
		if(!empty($menus)) return $menus;
		
		//启用的Addon
		$addons = Addon::getEnableAddons(1);
		
		//过滤菜单
		
		if(count($addons) > 0)
			foreach($addons as $appName=>$addon){
				if(isset($menus[$appName])) continue;
				$menulist = self::getMenuList($appName);
				if($menulist)
					$menus[$appName] = [
						'name'=>$addon['name'],
						'controls'=>$menulist,
					];
			}
		
		return $menus;
	}
	
	/**
     * 保存权限节点列表
	 *
     * @return array
     */
    private static function takeMenuList(): array
    {
		static $menuslist = [];
		
		if(!empty($menuslist)) return $menuslist;
		
		//非Debug模式下 取缓存值
		if(isDebug() === false){
			$menuslist = G('checkMenuList');
			if(!empty($menuslist)) return $menuslist;
		}
		
		//获取全部
		$menus = self::getAllMenuList();
		
		foreach($menus as $app){
			foreach($app['controls'] as $control){
				foreach($control['menus'] as $menu){
					$menuslist[] = $menus;
				}
			}
		}
		
		//存放节点缓存
		if(isDebug() === false)
			G('checkMenuList', $menuslist);
		
		return $menuslist;
	}
	
    /**
     * 初始化用户权限
	 *
     * @param boolean $force 是否重置系统权限
     */
    public static function applyUserAuth(array $auths = [],bool $force = false): array
    {
		//清除缓存
        if ($force) {
            G('nodeAuthList', null);
            G('nodeMenuList', null);
            G('nodeMethodList', null);
            G('checkAuthList', null);
            G('checkMenuList', null);
        }
		
		//获取个人权限
		empty($auths) && $auths = session('userAuths');
		
		//获取所有验证节点
		$authlist = self::takeAuthList();
		
		//超级管理员直接返回所有节点
		if(session('userRole') === 'Admin') return $authlist ?: [];
		
		foreach($auths as $key=>$auth){
			if(in_array($auth, $authlist)) continue;
			unset($auths[$key]);
		}
		
		return $auths ?: [];
    }
	
	/**
	 * 控制节点列表 
	 * 
	 * @param  $class 类名
	 * @return array  节点列表
	 */
	private static function getMethodList(string $appName,string $control): array
	{
		//判断操作列表
		if(empty(self::$methods[$control])){
			//合并菜单分组
			self::$methods[$control] = [];
			$objCacheList = self::eachController($appName);
			if(isset($objCacheList[$control]))
				$group = $objCacheList[$control]->hasConstant('classGroup') ? $objCacheList[$control]->getConstant('classGroup') : str_replace('Action', '', $objCacheList[$control]->getShortName());
				//过滤禁止读取权限
				if($objCacheList[$control]->hasMethod('__noAuth') === false)
					foreach($objCacheList[$control]->getMethods(\ReflectionMethod::IS_PUBLIC) as $method){
						//只提取当前控制器的操作
						$methodName = str_replace('\\', '/', $method->class);
						
						if($control == $method->class || (stripos($methodName, 'APP/Control') === 0 && stripos($methodName, '/Common') === false)){
							if($method->name[0] == '_'){
								continue;
							}
							
							//获取注释
							$comment = $method->getDocComment() ? preg_replace("/\s/", '', $method->getDocComment()): '';
							
							//操作名称
							$title = preg_replace('/^\/\*\*\*(.*?)\*.*?$/', '$1', $comment);
							
							//操作描述
							preg_match('|@describe(.*?)\*|isU', $comment, $matches);
							$describe = $matches[1] ?: '';
							
							//存放操作
							$methodinfo = [
								'group'	=> $group,
								'auth'  => stripos($comment, '@authtrue') !== false,
								'menu'  => stripos($comment, '@menutrue') !== false,
								'log'  => stripos($comment, '@logtrue') !== false,
								'title' => stripos($title, '@') === false ? $title : '',
								'describe' => $describe,
							];
							
							if($control == $method->class){
								self::$methods[$control][$method->name] = $methodinfo;
							}else{
								self::$methods[$control] = array_merge([$method->name=>$methodinfo], self::$methods[$control]);
							}
						}else{
							break;
						}
					}
		}
		return self::$methods[$control] ?: [];
	}
	
	/**
	 * 控制器扫描
	 *
	 * @param string $appName 项目名称
	 * @return array  
	 */
	private static function eachController(string $appName = null,string $type = null): array
	{
		if(isset(self::$objCacheList[$appName])) return self::$objCacheList[$appName];
		
		//获取APP目录
		$appPath = getAppPath($appName, $type);
		
		if($appPath){
			//APP自有控制器
			$ctrs = \Candy\Extend\Dir::getFiles($appPath.'Controls/', '*.php');
			
			//插件类控制器
			$pctrs = self::eachAddonController($appName);
			
			//合并控制器组
			$ctrs = array_merge($ctrs, $pctrs);
			
			$objCacheList = [];
			$i = 0;
			
			foreach ($ctrs as $file){
				if (!preg_match("|/(\w+)/(\w+)/Controls/(.+)\.class.php$|", $file, $matches)) continue;
				
				//获取APP目录除Common控制器
				if($matches[3] != 'Common'){
					if($matches[1] == 'Application'){
						$className = self::classCacheFile($matches[2], $matches[3]);
					}else{
						$className = self::classCacheFile($matches[2], $matches[3], 'addon');
					}
					
					$refClass = new \ReflectionClass($className);
					//检查是否全应用禁止权限检查
					if($refClass->hasMethod('closeAuth')){
						self::$objCacheList[$appName] = [];
						break;
					}else{
						//排序
						$order = $refClass->hasConstant('classOrder') ? $refClass->getConstant('classOrder') : $i;
						//防止覆盖
						while(isset($objCacheList[$order])){
							$order++;
						};
						$objCacheList[$order] = [$className, $refClass];
						$i++;
					}
				}
			}
			$objNum = count($objCacheList);
			if($objNum > 0){
				ksort($objCacheList);
				do{
					$cache = array_pop($objCacheList);
					list($class, $ref) = $cache;
					self::$objCacheList[$appName][$class] = $ref;
					$objNum--;
				}while($objNum > 0);
			}
		}
		return self::$objCacheList[$appName] ?: [];
	}
	
	/**
	 * 插件控制器扫描
	 *
	 * @param string $appName 项目名称
	 * @return array  
	 */
	private static function eachAddonController(string $appName): array
	{
		//p(Hook::getEnableHooks(1));
		return [];
	}
	
	/**
     * 创建控制器缓存
	 *
     * @param string $appName 项目名称
     * @param string $className 控制器名称
     * @param string $type 类型
     */
	private static function classCacheFile(string $appName,string $className,string $type = 'app'): string
	{
		//目录
		$appPath = CANDYROOT . ($type == 'app' ? 'Application/' : 'Addons/') . $appName .'/';
		$isAddon = $type == 'app' ? false : true;
		$srccontrolerfile = $appPath .'Controls/'. $className .'.class.php';
		
		//获取最终的类名和目录
		$classInfo = App::getClassInfo($className, $isAddon, $appName);
		
		//处理父类
		App::commonControler($appPath .'Controls/', $classInfo['path'], $appName);
		App::controler($srccontrolerfile, $classInfo['path'], $className, $appName);
		$className = empty($classInfo['extendName']) ? $classInfo['className'] : $classInfo['extendName'];
		return $className;
	}
	
	/**
     * 驼峰转下划线规则
	 *
     * @param string $node 节点名称
     * @return string
     */
    private static function parseString(string $node): string
    {
        if (count($nodes = explode('/', $node)) > 1) {
            $dots = [];
            foreach (explode('.', $nodes[1]) as $dot) {
                $dots[] = trim(preg_replace("/[A-Z]/", "_\\0", $dot), "_");
            }
            $nodes[1] = join('.', $dots);
        }
        return join('/', $nodes);
    }
}